package com.tosee.ws.impl;

import java.util.List;
import java.util.Locale;

import javax.annotation.Resource;
import javax.ws.rs.Path;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Service;

import com.tosee.exception.ConteneurException;
import com.tosee.exception.GeneriqueException;
import com.tosee.model.allocine.AlloEpisode;
import com.tosee.model.allocine.AlloSaison;
import com.tosee.model.allocine.AlloSerie;
import com.tosee.model.dto.AbstractDto;
import com.tosee.model.dto.AuthentificationDto;
import com.tosee.model.dto.CodeDto;
import com.tosee.model.dto.ListeCodeDto;
import com.tosee.model.dto.RechercheDto;
import com.tosee.model.dto.UserDto;
import com.tosee.process.impl.BusinessProcessProvider;
import com.tosee.utils.MessageHandler;
import com.tosee.ws.IWSTosee;
import com.tosee.ws.model.WSBoolean;
import com.tosee.ws.model.WSEpisode;
import com.tosee.ws.model.WSErreur;
import com.tosee.ws.model.WSInteger;
import com.tosee.ws.model.WSListe;
import com.tosee.ws.model.WSObject;
import com.tosee.ws.model.WSParamMethode;
import com.tosee.ws.model.WSSaison;
import com.tosee.ws.model.WSSerie;
import com.tosee.ws.model.WSUser;

/**
 * @author robint
 * @param <T>
 */
@Service
@Path("/methode/")
// @WebService(endpointInterface = "com.tosee.ws.IWSTosee", targetNamespace = "http://ws.webServiceTosee.xxx.com/")
public class WSTosee<T> implements IWSTosee {

    /**
     * LOGGER.
     */
    private final Log LOGGER = LogFactory.getLog(WSTosee.class);

    /**
     * Fournisseur de BP
     */
    @Resource
    private BusinessProcessProvider businessProcessProvider;

    @Override
    public WSListe<WSSerie> getSeriesByName(WSParamMethode _paramMethode, String _name) throws GeneriqueException {
        WSListe<WSSerie> retour = null;

        // Construction du DTO portant les informations utilisateur
        RechercheDto recherche = paramMethodeToDto(_paramMethode, RechercheDto.class);
        recherche.setNom(_name);
        try {

            List<AlloSerie> liste = businessProcessProvider.getSerieBusinessProcess()
                    .getSeriesByRechercheNom(recherche);
            if(CollectionUtils.isNotEmpty(liste)) {
                retour = new WSListe<WSSerie>(liste.size());
                for(AlloSerie serie : liste) {
                    retour.ajouterObjet(new WSSerie(serie));
                }
            }
        } catch (Exception e) {
            retour = gererExceptions(_paramMethode, e, WSListe.class);

        }
        return retour;
    }

    @Override
    public WSUser authentifier(WSParamMethode _paramMethode) throws GeneriqueException {
        WSUser retour = null;

        // Construction du DTO portant les informations utilisateur
        AuthentificationDto auth = paramMethodeToDto(_paramMethode, AuthentificationDto.class);
        try {
            retour = new WSUser(businessProcessProvider.getUserBusinessProcess().authentifier(auth));
        } catch (Exception e) {
            retour = gererExceptions(_paramMethode, e, WSUser.class);
        }
        return retour;
    }

    @Override
    public WSListe<WSSerie> chargerGrappeSerieUser(WSParamMethode _paramMethode, Integer _idUser)
            throws GeneriqueException {
        WSListe<WSSerie> retour = null;
        try {
            // List<SerieDto> listSerie = businessProcessProvider.getUserBusinessProcess().chargerGrappeSerieUser(
            // _paramMethode, _idUser);
        } catch (Exception e) {
            retour = gererExceptions(_paramMethode, e, WSListe.class);
        }
        return retour;
    }

    @Override
    public WSSerie getAlloSerie(WSParamMethode _paramMethode, Integer _code) throws GeneriqueException {
        WSSerie retour = null;

        // Construction du DTO portant les informations utilisateur
        RechercheDto recherche = paramMethodeToDto(_paramMethode, RechercheDto.class);
        recherche.setCode(_code);
        try {
            AlloSerie liste = businessProcessProvider.getSerieBusinessProcess().getInfoSerieByRechercheCode(recherche);
            if(liste != null) {
                retour = new WSSerie(liste);
            }
        } catch (Exception e) {
            retour = gererExceptions(_paramMethode, e, WSSerie.class);
        }
        return retour;
    }

    @Override
    public WSBoolean ajouterSerie(WSParamMethode _paramMethode, Integer _code) throws GeneriqueException {
        WSBoolean retour = new WSBoolean(false);

        CodeDto codeDto = paramMethodeToDto(_paramMethode, CodeDto.class);
        codeDto.setCode(_code);
        try {
            Boolean exec = businessProcessProvider.getUserBusinessProcess().getAjouterAbonnementSerie(codeDto);
            retour.setValeur(exec);

        } catch (Exception e) {
            retour = gererExceptions(_paramMethode, e, WSBoolean.class);
        }
        return retour;
    }

    @Override
    public WSBoolean ajouterEpisodeVu(WSParamMethode _paramMethode, List<Integer> _listeCode) throws GeneriqueException {
        WSBoolean retour = new WSBoolean(false);

        ListeCodeDto listeCode = paramMethodeToDto(_paramMethode, ListeCodeDto.class);
        listeCode.setListeCode(_listeCode);

        try {
            Boolean exec = businessProcessProvider.getUserBusinessProcess().ajouterEpisodeVu(listeCode);
            retour.setValeur(exec);

        } catch (Exception e) {
            retour = gererExceptions(_paramMethode, e, WSBoolean.class);
        }
        return retour;
    }

    @Override
    public WSSaison getAlloSaison(WSParamMethode _paramMethode, Integer _code) throws GeneriqueException {
        WSSaison retour = null;

        // Construction du DTO portant les informations utilisateur
        RechercheDto recherche = paramMethodeToDto(_paramMethode, RechercheDto.class);
        recherche.setCode(_code);
        try {
            AlloSaison allosaison = businessProcessProvider.getSerieBusinessProcess().getInfoSaisonByRechercheCode(
                    recherche);
            if(allosaison != null) {
                retour = new WSSaison(allosaison);
            }
        } catch (Exception e) {
            retour = gererExceptions(_paramMethode, e, WSSaison.class);
        }
        return retour;
    }

    @Override
    public WSEpisode getAlloEpisode(WSParamMethode _paramMethode, Integer _code) throws GeneriqueException {
        WSEpisode retour = null;

        // Construction du DTO portant les informations utilisateur
        RechercheDto recherche = paramMethodeToDto(_paramMethode, RechercheDto.class);
        recherche.setCode(_code);
        try {
            AlloEpisode alloEpisode = businessProcessProvider.getSerieBusinessProcess().getInfoEpisodeByRechercheCode(
                    recherche);
            if(alloEpisode != null) {
                retour = new WSEpisode(alloEpisode);
            }
        } catch (Exception e) {
            retour = gererExceptions(_paramMethode, e, WSEpisode.class);
        }
        return retour;
    }

    /**********************************
     **** WS de creations ****
     ***********************************/
    @Override
    public WSInteger creerUtilisateur(WSParamMethode _paramMethode, WSUser _wsuser) {
        WSInteger retour = null;

        // Construction du DTO portant les informations utilisateur
        UserDto userDto = paramMethodeToDto(_paramMethode, UserDto.class);

        if(_wsuser != null) {
            try {
                Integer id = (businessProcessProvider.getUserBusinessProcess().creerUtilisateur(_wsuser.toDto(userDto)));
                retour = new WSInteger(id);
            } catch (Exception e) {
                retour = gererExceptions(_paramMethode, e, WSInteger.class);
            }
        }
        return retour;

    }

    /**********************************
     **** utilitaires ****
     ***********************************/

    /**
     * Transforme un WSParamMethode en dto.
     * 
     * @param <T> Classe de l'objet de retour
     * @param _paramMethode les paramètres "constants" des web méthodes (login, mdp, lcoale, ...)
     * @param _dtoClass la classe de l'objet à retourner
     * @return un DTO contenant les informations de l'utilisateur
     */
    private <T extends AbstractDto> T paramMethodeToDto(final WSParamMethode _paramMethode, final Class<T> _dtoClass) {

        T retour = null;
        try {
            retour = _dtoClass.newInstance();
            if(_paramMethode != null) {
                // Login mdp
                AuthentificationDto authentificationDto = new AuthentificationDto(_paramMethode.getLogin(),
                        _paramMethode.getPassword());
                retour.setAuthentification(authentificationDto);

                // Locale
                if(StringUtils.isNotBlank(_paramMethode.getLocale())) {
                    // FIXME Mettre la locale par défaut si celle passée en paramètre n'est pas supportée
                    Locale locale = new Locale(_paramMethode.getLocale());
                    retour.setLocaleUser(locale);
                }
                // Locale par défaut
                else {
                    retour.setLocaleUser(MessageHandler.getInstance().getDefaultLocale());
                }
            }
        } catch (InstantiationException e1) {
            if(LOGGER.isErrorEnabled()) {
                LOGGER.error("Problème lors de la transformation des paramètres de méthodes en DTO.", e1);
            }
        } catch (IllegalAccessException e1) {
            if(LOGGER.isErrorEnabled()) {
                LOGGER.error("Problème lors de la transformation des paramètres de méthodes en DTO.", e1);
            }
        }

        return retour;
    }

    /**
     * Gère les exceptions pour les transformer en WSErreur.
     * 
     * @param _paramMethode les paramètres "constants" des web méthodes (login, mdp, offre, ...)
     * @param e l'exception à gérer.
     * @return un WSObject contenant les exceptions transformées en erreur
     */
    private <T extends WSObject> T gererExceptions(WSParamMethode _paramMethode, Exception e, Class<T> _wsClass) {

        T wsObject = null;
        try {
            wsObject = (T) _wsClass.newInstance();
        } catch (InstantiationException e1) {
            if(LOGGER.isErrorEnabled()) {
                LOGGER.error("Problème lors de la restitution des erreurs.", e1);
            }
        } catch (IllegalAccessException e1) {
            if(LOGGER.isErrorEnabled()) {
                LOGGER.error("Problème lors de la restitution des erreurs.", e1);
            }
        }

        if(wsObject != null) {

            // Conteneur ?
            if(e instanceof ConteneurException) {
                // On boucle sur les exceptions
                if(!((ConteneurException) e).isEmpty()) {
                    for(Throwable ex : ((ConteneurException) e).getListeExceptions()) {
                        wsObject.ajouterErreur(transformerException(_paramMethode, ex));
                    }
                }
            }
            // Exception "classique"
            else {
                wsObject.ajouterErreur(transformerException(_paramMethode, e));
            }
        }

        // Dans le cas d'un booléen, on met la valeur à faux
        if(wsObject instanceof WSBoolean) {
            ((WSBoolean) wsObject).setValeur(Boolean.FALSE);
        }
        return (T) wsObject;
    }

    /**
     * Transforme une exception en une WSErreur
     * 
     * @param _paramMethode les paramètres "constants" des web méthodes (login, mdp, offre, ...)
     * @param ex l'exception à transformer (ne doit pas être de type ConteneurException)
     * @return une WSErreur
     */
    private WSErreur transformerException(WSParamMethode _wsParamMethode, Throwable ex) {
        WSErreur wsErreur = new WSErreur();

        if(ex instanceof GeneriqueException) {
            if(LOGGER.isInfoEnabled()) {
                LOGGER.info("L'erreur suivante est survenue : ", ex);
            }
            wsErreur.setMessage(ex.getMessage());
            wsErreur.setCode(((GeneriqueException) ex).getCodeErreur());
        } else {
            if(LOGGER.isErrorEnabled()) {
                LOGGER.error("L'erreur suivante est survenue : ", ex);
            }
            Locale locale = null;
            if(_wsParamMethode != null && StringUtils.isNotBlank(_wsParamMethode.getLocale())) {
                locale = new Locale(_wsParamMethode.getLocale());
            }
            String codeErreur = MessageHandler.getInstance().getMessage("erreur.autre.inconnue.code", locale);
            wsErreur.setCode(Integer.parseInt(codeErreur));
            wsErreur.setMessage(MessageHandler.getInstance().getMessage("erreur.autre.inconnue.msg", locale));
        }
        return wsErreur;
    }

    /**
     * Transforme une exception en String
     * 
     * @param ex l'exception à transformer (ne doit pas être de type ConteneurException)
     * @return une chaine de caractère contenant les erreurs.
     */
    private String transformerException(Throwable ex) {
        StringBuilder builder = new StringBuilder(200);

        if(ex instanceof ConteneurException) {
            for(Throwable e : ((ConteneurException) ex).getListeExceptions()) {
                if(builder.length() != 0) {
                    builder.append("\n");
                }
                if(e instanceof GeneriqueException) {
                    builder.append(((GeneriqueException) e).getCodeErreur());
                    builder.append(" : ");
                }
                builder.append(e.getMessage());
            }
        } else {
            if(builder.length() != 0) {
                builder.append("\n");
            }
            if(ex instanceof GeneriqueException) {
                builder.append(((GeneriqueException) ex).getCodeErreur());
                builder.append(" : ");
            }
            builder.append(ex.getMessage());
        }
        return builder.toString();
    }

}
